package com.leonzhangxf.ioc.util;

import com.leonzhangxf.ioc.Injector;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author leonzhangxf
 */
public class ClassUtils {

    /**
     * The package separator character: {@code '.'}.
     */
    private static final char PACKAGE_SEPARATOR = '.';

    /**
     * The path separator character: {@code '/'}.
     */
    private static final char PATH_SEPARATOR = '/';

    /**
     * The ".class" file suffix.
     */
    public static final String CLASS_FILE_SUFFIX = ".class";

    public static String convertClassNameToResourcePath(String className) {
        return className.replace(PACKAGE_SEPARATOR, PATH_SEPARATOR);
    }

    public static String convertResourcePathToClassName(String path) {
        return path.replace(PATH_SEPARATOR, PACKAGE_SEPARATOR);
    }

    public static Set<Class<?>> getClassesFromPackage(String pkg) {
        String location = convertClassNameToResourcePath(pkg);
        return getClassesFromLocation(location);
    }

    /**
     * 扫描路径下所有的class文件
     *
     * @param location location
     * @return all class scanned
     */
    public static Set<Class<?>> getClassesFromLocation(String location) {
        Set<Class<?>> classes = new LinkedHashSet<>();

        String pkgDirName = location;
        try {
            Enumeration<URL> urls = getClassLoader().getResources(pkgDirName);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                String protocol = url.getProtocol();
                if ("file".equals(protocol)) {
                    // 如果是以文件的形式保存在服务器上
                    // 获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), StandardCharsets.UTF_8);
                    findClassesByFile(location, filePath, classes);
                } else if ("jar".equals(protocol)) {
                    // 如果是jar包文件
                    JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile();
                    findClassesByJar(location, jar, classes);
                }
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }

        return classes;
    }

    private static ClassLoader getClassLoader() {
        return ClassUtils.class.getClassLoader();
    }


    /**
     * 扫描包路径下的所有class文件
     *
     * @param pkgName 包名
     * @param pkgPath 包对应的绝对地址
     * @param classes 保存包路径下class的集合
     */
    private static void findClassesByFile(String pkgName, String pkgPath, Set<Class<?>> classes)
        throws ClassNotFoundException {
        File dir = new File(pkgPath);
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }

        // 过滤获取目录，or class文件
        File[] dirfiles = dir.listFiles(pathname -> pathname.isDirectory()
            || pathname.getName().endsWith(CLASS_FILE_SUFFIX));


        if (dirfiles == null || dirfiles.length == 0) {
            return;
        }

        String className;
        Class<?> clz;
        for (File f : dirfiles) {
            if (f.isDirectory()) {
                findClassesByFile(pkgName + "." + f.getName(),
                    pkgPath + PATH_SEPARATOR + f.getName(),
                    classes);
                continue;
            }

            // 获取类名，干掉 ".class" 后缀
            className = f.getName();
            className = className.substring(0, className.length() - 6);

            // 加载类
            clz = loadClass(pkgName + PACKAGE_SEPARATOR + className);
            if (clz != null) {
                classes.add(clz);
            }
        }
    }


    /**
     * 扫描包路径下的所有class文件
     *
     * @param pkgName 包名
     * @param jar     jar文件
     * @param classes 保存包路径下class的集合
     */
    private static void findClassesByJar(String pkgName, JarFile jar, Set<Class<?>> classes)
        throws ClassNotFoundException {
        String pkgDir = convertClassNameToResourcePath(pkgName);

        Enumeration<JarEntry> entry = jar.entries();

        JarEntry jarEntry;
        String name, className;
        Class<?> claze;
        while (entry.hasMoreElements()) {
            jarEntry = entry.nextElement();

            name = jarEntry.getName();
            if (name.charAt(0) == '/') {
                name = name.substring(1);
            }


            if (jarEntry.isDirectory() || !name.startsWith(pkgDir) || !name.endsWith(CLASS_FILE_SUFFIX)) {
                // 非指定包路径， 非class文件
                continue;
            }


            // 去掉后面的".class", 将路径转为package格式
            className = name.substring(0, name.length() - 6);
            claze = loadClass(className.replace(PATH_SEPARATOR, PACKAGE_SEPARATOR));
            if (claze != null) {
                classes.add(claze);
            }
        }
    }

    private static Class<?> loadClass(String fullClzName) throws ClassNotFoundException {
        fullClzName = convertResourcePathToClassName(fullClzName);
        return Thread.currentThread().getContextClassLoader().loadClass(fullClzName);
    }
}
